﻿

using System;
using System.Threading;
using MiningCore.Contracts;
using MiningCore.Extensions;
using MiningCore.Native;
using NLog;

namespace MiningCore.Crypto.Hashing.Equihash
{
    public unsafe class EquihashSolver
    {
        private EquihashSolver(int maxConcurrency)
        {
            // we need to limit concurrency here due to the enormous memory
            // requirements of the equihash algorithm (up 1GB per thread)
            sem = new Semaphore(maxConcurrency, maxConcurrency);
        }

        private static readonly ILogger logger = LogManager.GetCurrentClassLogger();

        public static Lazy<EquihashSolver> Instance { get; } = new Lazy<EquihashSolver>(() =>
            new EquihashSolver(maxThreads));

        private static int maxThreads = 1;

        public static int MaxThreads
        {
            get => maxThreads;
            set
            {
                if(Instance.IsValueCreated)
                    throw new InvalidOperationException("Too late: singleton value already created");

                maxThreads = value;
            }
        }

        private readonly Semaphore sem;

        /// <summary>
        /// Verify an Equihash solution
        /// </summary>
        /// <param name="header">header including nonce (140 bytes)</param>
        /// <param name="solution">equihash solution (excluding 3 bytes with size, so 1344 bytes length) - Do not include byte size preamble "fd4005"</param>
        /// <returns></returns>
        public bool Verify(byte[] header, byte[] solution)
        {
            Contract.RequiresNonNull(header, nameof(header));
            Contract.Requires<ArgumentException>(header.Length == 140, $"{nameof(header)} must be exactly 140 bytes");
            Contract.RequiresNonNull(solution, nameof(solution));
            Contract.Requires<ArgumentException>(solution.Length == 1344, $"{nameof(solution)} must be exactly 1344 bytes");

            logger.LogInvoke();

            try
            {
                sem.WaitOne();

                fixed(byte *h = header)
                {
                    fixed(byte *s = solution)
                    {
                        return LibMultihash.equihash_verify(h, header.Length, s, solution.Length);
                    }
                }
            }

            finally
            {
                sem.Release();
            }
        }
    }
}
